<?php

/**
 * 易生支付（小程序，PCweb收银台）
 * @author chenshubo
 */
namespace App\Service\VendorClient\EasyPayApi;

use App\Model\PssLogModel\MicroServiceLog;
use App\Service\AppService\SystemCacheConfigService;
use App\Service\BaseService;
use App\Service\Code\Normal;
use App\Service\HttpServer\Http;

class EasyPayApiClient extends BaseService
{ 
    const PAY_TYPE = 5;
    const account = 'easy_pay';
    /**
     * 最大文件加密块
     */
    const MAX_ENCRYPT_BLOCK = 117;
    
    /**
     * 最大文件解密块
     */
    const MAX_DECRYPT_BLOCK = 128;
    
    private $config;
    public function __construct()
    {
        $this->config   = SystemCacheConfigService::instance()->getCacheInfoBySystemCode(self::account);
        $this->config = [
            'gateway'                       => 'https://neopay.bhecard.com:8801/api/',
            'merchant_number'               => '031827',
            'merchant_login_id'             => 'YS031827',
            'merchant_password'             => 'ab9de2495b7743ada9e8d472a0912ed8',
            'version'                       => '1',
            'ys_private_key'                => 'MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANSX8G8m1JYiJ0Qay2bT8X5cXmPVOTCb7R0uCq2266i48FKYbDD45Le2Sxn/UYFXZKjkkWMdntq99/zV4K7MQiU6lo6kriYnN/RYfYjyuS7++jfGDyxiSsKJozsywV/DKniKnlF63XGMsNd80dWnqei8qNorh0sako1P0PRmM6afAgMBAAECgYAwzQAAQ+Wg4Q4qcAwo+pYA76srN5L9OY3gmOUSo17NSEbCbwLG6xt1oH705MsM6cbNjkphejcw91Kp017UQUcdYYUpgFHNWEeFf7qT7hbFiKiAmmZYEIQkbgqQgKhNCa/OMQovzupY4RxGrQhJAOy34qavrDmJqWxVncDgrATsQQJBAO+OF0e3nw38dohU86dIePsWjISbv6cfyuDWT7dPDCCZN7DWTIf5qwJiVblekLlCk9AEzEMnCn4ZC92L2obMLZkCQQDjMAej1SWF66/JfJVrYsOAja3K9tH3VebPRoMmE69JluerJiQ9qKD5/CZCWEezkH+c9WxEYckr25tGu5B/iej3AkEAh19ponqOpI21wicXpcHbtDCy6cFGFcBumo4hcDp4ZPeQtBr2FqUtUWSWgvGAoWCRPS1o7M3kLQwkW2dL7370iQJAcz7hEUXFpBq78IzHS1Mtc1/rgmMUfdBQpmLRkpY3vC/V02ckyTZqeoVjbxJTRQ6O/bMtub6VCy96pDXvhhMSXwJAbRiGRVrcamSTMZcKLm7Wliwf5jPzs/ayUOd8/lxzHmet8L8STvQYh75MMkNoNbWNc4OmSd4XSxNx4PixLsW/Qw==',
            'userRegister'                  => '/merchants/register',
            'userLogin'                     => '/merchants/login',
            'sendMsg'                       => '/sendMsg',
            'userOpenPerson'                => '/merchant/privateperson',//进件开户接口
            'userIdentifyCardVerify'        => '/transactions/authentication/police/verify',//身份证二要素认证
            'orderCashWebWeiXinPay'         => '/transactions/scan_payment/wechat/code/create',//PC WEB 微信扫码支付
            'orderCashWebAliPay'            => '/transactions/scan_payment/ali/code/create',//PC WEB 支付宝扫码支付
            'orderMnAppPay'                 => '/transactions/scan_payment/wechat/app/create',//小程序支付
            'orderSplitApply'               => '/api/merge_payment/split_order/create',//拆单
            'orderSplitApplyQuery'          => '/api/merge_payment/split_order/search',//拆单申报查询
            'orderSplitApplyCustom'         => '/api/transactions/commit_custom/create',//拆单申报
            'orderMnAppPayRefundNotifyUrl'  => 'http://testpssnotify.indoorbuy.com/Notify/index/userType/easypay/messageType/orderMnAppPayRefundNotify',//小程序支付回调
            'orderCashWxNotifyUrl'          => 'http://testpssnotify.indoorbuy.com/Notify/index/userType/easypay/messageType/orderCashWxNotify',//PC WEB收银支付成功回调
            'orderCashAliPayNotifyUrl'      => 'http://testpssnotify.indoorbuy.com/Notify/index/userType/easypay/messageType/orderCashAliPayNotify',//PC WEB收银支付成功回调
            'orderApplyCustomNotifyUrl'     => 'http://testpssnotify.indoorbuy.com/Notify/index/userType/easypay/messageType/orderApplyCustomNotify',//支付单申报回调
        ];
    }

    /**
     * PC商城支付
     * @param array $param
     * @return void
     */
    public function OrderPay(array $params = [])
    {
//        return $this->orderCashWebWeiXinPay();
        $orderInfo = $params['payInfo'];
        $data = [
            'encryptInfo'   => [
                'identityInfo' => [
                    'name' => $orderInfo['buyer_realname'],
                    'number' => $orderInfo['buyer_idcard'],
                    'type' => '01',
                    'mobile' => $orderInfo['buyer_phone']
                ],
            ],
            'data'  => [
                'paymentType'       => 'wxScanPay',//类型 （微信线上扫码-“wxScanPay”；公众号支付-“gzhPay”）
                'tradeamt'          => (string)$orderInfo['pay_money'],//交易金额(单位元)
                'body'              => '在线支付下单',//备注
                'backUrl'           => $this->config['orderCashWxNotifyUrl'],//支付回调地址
            ],
//            'trace' =>  $orderInfo['order_sn']
        ];
        return $this->execute('orderCashWebWeiXinPay',$data);
    }

    public function generateOrderSn()
    {
        return $this->config['merchant_number'].date("YmdHis").rand(100000,999999);
    }
    
    /**
     * 用户注册
     * @param array $param
     */
    public function userRegister( array $param = [] )
    {
        $data = [
            'name'                      => '小陈哥',//姓名
            'phone'                     => '15228257236',//手机号
            'password'                  => '123456',//密码
            'proxyMerchantNumber'       => $this->config['merchant_number'],//代理商户号
        ];
        return $this->execute('userRegister',$data);
    }
    
    /**
     * 用户登录
     * @param array $param
     */
    public function userLogin(array $param = [])
    {
        $redis = $this->getRedisObj();
        $cache_key = 'yisheng_access_token_'.$this->config['merchant_login_id'];
        $encryptInfo = $redis->get($cache_key);
        $encryptInfo = json_decode($encryptInfo,true);
        if( !empty($encryptInfo) ) {
            return Normal::returnCode(Normal::SUC,"获取token成功",$encryptInfo);
        }
        $data = [
            'encryptInfo'   => [
            ],
            'data'  => [
                'loginId'   => $this->config['merchant_login_id'],
                'password'  => $this->config['merchant_password'],
            ]
        ];
        $return = $this->execute('userLogin',$data);
        if( $return['code'] == Normal::SUC ) {
            $redis->set($cache_key,json_encode($return['data']['encryptInfo']),30*60);
            return Normal::returnCode(Normal::SUC,"获取token成功",$return['data']['encryptInfo']);
        }
        return $return;
    }
    
    /**
     * 获取短信验证码
     * @param array $param
     */
    public function sendMsg( array $param = [] )
    {
        $data = [
            'phoneNo'   => '小陈哥',//用于接收验证码短信的手机号
        ];
        return $this->execute('sendMsg',$data);
    }
    
    /**
     * 个人跨境类商户调用此接口完成商户进件
     * @param array $param
     */
    public function userOpenPerson( array $param = [] )
    {
        $data = [
            'encryptInfo'   => [
                'name'          => '小陈哥',//个人商户姓名
                'identity'      => '小陈哥',//身份证号
                'phone'         => '小陈哥',//手机号
                'msgCode'       => '小陈哥',//短信验证码
                'address'       => '小陈哥',//地址
                'bankAccountNo' => '小陈哥',//银行账号
            ],
            'data'  => [
                'identityFrom'  => '',//身份证有效期起始日
                'identityTo'    => '',//身份证有效期终止日
                'picInfoList'   => '',//json 格式字符串
                'bankId'        => '',//银行编号（具体请查看附件）
            ]
        ];
        return $this->execute('userOpenPerson',$data);
    }
    
    /**
     * 身份验证接口-根据证件，姓名调用公安接口验证
     * @param array $param
     */
    public function userIdentifyCardVerify( array $param = [] )
    {
        $data = [
            'encryptInfo'   => [
                'accName'       => '小陈哥',//姓名
                'certNo'        => '510521198908087579',//身份证号
                'identityType'  => '01',//证件类型(00 其他,01 大陆居民身份证)
            ],
            'data'  => [
            ]
        ];
        return $this->execute('userIdentifyCardVerify',$data);
    }
    
    /**
     * 微信支付扫码收银台
     * @param array $param
     */
    public function orderCashWebWeiXinPay( array $param = [] )
    {
        $data = [
            'encryptInfo'   => [
            ],
            'data'  => [
                'paymentType'       => 'wxScanPay',//类型 （微信线上扫码-“wxScanPay”；公众号支付-“gzhPay”）
                'tradeamt'          => '0.01',//交易金额(单位元)
                'body'              => '在线微信扫码支付',//备注
                'backUrl'           => $this->config['orderCashWxNotifyUrl'],//支付回调地址
            ]
        ];
        return $this->execute('orderCashWebWeiXinPay',$data);
    }
    
    /**
     * 支付宝扫码收银台
     * @param array $param
     */
    public function orderCashWebAliPay( array $param = [] )
    {
        $data = [
            'encryptInfo'   => [
            ],
            'data'  => [
                'tradeamt'  => '0.01',
                'body'      => '在线支付宝扫码支付',//备注
                'backUrl'   => $this->config['orderCashAliPayNotifyUrl'],//支付回调地址
            ],
            
        ];
        return $this->execute('orderCashWebAliPay',$data);
    }
    
    /**
     * 微信小程序支付
     * @param array $param
     */
    public function orderMnAppPay( array $param = [] )
    {
        $data = [
            'encryptInfo'   => [
                //'identityInfo'  => ''
            ],
            'data'  => [
                'openid'            => 'okC5d459PPSISZgDkOpj-ZFxl8-w',//微信openid
                'body'              => '在线支付',
                'tradeamt'          => '0.01',//交易金额(单位元)
                'orgDeviceIp'       => '127.0.0.1',//设备ip地址
                'backUrl'           => $this->config['orderMnAppPayRefundNotifyUrl'],//支付回调地址
                'appId'             => 'wxe301a3f016ddec1d',//商户APPID
            ]
        ];
        return $this->execute('orderMnAppPay',$data);
    }
    
    /**
     * 支付退款接口
     * @param array $param
     */
    public function orderPayRefund( array $param = [] )
    {
        $data = [
            'encryptInfo'   => [
            ],
            'data'  => [
                'businessType'              => 'zwrefund',//类型 （撤销交易-“cancel”，仅对当日交易可以撤销，日切时间为渠道服务器的00:00；退货交易-“zwrefund”（退款，按照传入金额退款，总退款次数不超过50次））
                'originalSourceTrace'       => '114544546',//原交易号
                'originalTransactionType'   => 'zwrefund',//原交易类型
                'tradeamt'                  => '0.01',//退款金额
            ]
        ];
        return $this->execute('orderPayRefund',$data);
    }
    
    /**
     * 合单支付创建拆单明细
     * @param array $param
     */
    public function orderPaySplitApplyDeclare( array $param = [] )
    {
        //拆单明细
        $payOrderDetails = [
            [
                'merchantNumber'    => '',//子订单对应的主体商户号,必填
                'orderNo'           => '123456',//子订单号，必填
                'title'             => '',//子订单标题，必填
                'subject'           => 'i',//子订单描述，必填
                'transAmt'          => '',//transAmt	
            ]
        ];
        $data = [
            'encryptInfo'   => [
                'payTransactionNo'  => '121456447544456',
                'payOrderDetails'   => json_encode($payOrderDetails)
            ],
            'data'  => [
            ]
        ];
        
        //拆分订单申报结果
        /****
         *     encryptInfo内容说明
                                            字段名称	标识	请求	应答	备注
                                            支付交易号	payTransactionNo	M
                                            订单明细列表	payOrderDetails	M		订单明细列表
                                            子订单商户号	payOrderDetail.merchantNumber	M		子订单对应主体商户号
                                            子订单号	payOrderDetail.orderNo	M		子订单对应订单号
                                            子订单支付交易号	payOrderDetail.paymentNo	M		订单明细列表
                                            子订单标题	payOrderDetail.title	M		子订单标题
                                            子订单描述	payOrderDetail.subject	M		子订单描述
                                            子订单金额	payOrderDetail.transAmt	M		子订单金额
                                            父订单支付交易号	payOrderDetail.refMergePaymentNo	M		父订单支付交易号，即合单支付交易号
                                            交易状态	payOrderDetail.status	M		交易状态，processing：交易进行中，success：交易成功，failure 交易失败
         * 
         * @var unknown
         */
        $orderPaySplitApplyDeclareResult = $this->execute('orderSplitApply',$data);
        if( $orderPaySplitApplyDeclareResult['code'] != Normal::SUC ) {
            return $orderPaySplitApplyDeclareResult;
        }
        
        $encryptInfo = $orderPaySplitApplyDeclareResult['encryptInfo'];
        $customOrderList = [];
        foreach ($encryptInfo['payOrderDetail'] as $item) 
        {
            $customOrderList[] = [
                'amount'                => $item['transAmt'],//支付金额（单位：元），必填
                'competentCustomCode'   => '6666',//主管海关代码，广州海关必填（附录2.1-2.3)
                'customType'            => 'D',//购汇主体类型 C 对公 D 对私，必填
                'isBondedMode'          => 'Y',//是否保税模式，Y 是，N 否
                'orderNo'               => $item['orderNo'],//电商平台订单号，必填
                'payTime'               => get13TimeStamp(),
                'payTransactionNo'      => $item['paymentNo'],//支付交易号，必填，订单推送接口的流水号（trace）或国内支付接口的流水号（trace）若是拆单后子订单报关，则传子订单的支付单号 注意：宁波和杭州海关拆单报关要传主支付单的wxTransactionId
                'payerCardNo'           => '131002199104162850',//付款人身份证号
                'payerName'             => 'ZhangQi',//付款人姓名
                'payerPhone'            => '18801104615',//付款人真实电话
                'planPushCustomCode'    => 'FJ',//拟报海关代码，必填（海关代码对照表见附录1.6）
                'quarantineCode'        => 'Y',//检疫机构代码，广州海关必填（附录2.4）
                'preEbpCode'            => 'XXXXX',//电商平台备案号
            ];
        }
        $data = [
            'encryptInfo'   => [
                'orderList'     => json_encode($customOrderList)
            ],
            'data'  => [
            ]
        ];
    }
    
    /**
     * 订单支付拆单查询
     * @param array $param
     */
    public function orderPaySplitApplyDeclareQuery( array $param = [] )
    {
        $data = [
            'encryptInfo'   => [
            ],
            'data'  => [
                'payTransactionNo'  => '主单支付流水号',
            ]
        ];
        return $this->execute('orderSplitApplyQuery',$data);
    }
    
    /**
     * 订单支付拆单报关查询
     * @param array $param
     */
    public function orderPaySplitApplyCustomQuery( array $param = [] )
    {
        $data = [
            'encryptInfo'   => [
            ],
            'data'  => [
                'pageSize'          => '100',
                'pageNumber'        => 1,
                'orderNo'           => '11111',//电商平台的订单编号
                'payTransactionNo'  => '11111',//易生返回的支付交易号
            ]
        ];
        return $this->execute('orderSplitApplyQuery',$data);
    }
    
    /**
     * 易生支付签名
     * @param string $method
     * @param array $param
     * @param bool $isSign
     */
    private function getSign(string $method,array $param = [],bool $isSign = true )
    {
        $trace = $param['trace'] ?? $this->config['merchant_number'].date("YmdHis").rand(100000,999999);
        $data = [
            'merchantNumber'    => $this->config['merchant_number'],
            'trace'             => $trace,
            'version'           => $this->config['version'],
        ];
        
        //需要获取accessToken
        if( !in_array($method,['userLogin']) ) 
        {
            $result_user_login = $this->userLogin();
            if( $result_user_login['code'] != Normal::SUC ) {
                return $result_user_login;
            }
            
            $encryptInfo = $param['encryptInfo'] ?? [] ;
            $encryptInfo = array_merge($encryptInfo,[
                'loginId'       => $this->config['merchant_login_id'],
                'accessToken'   => $result_user_login['data']['accessToken'],
            ]);
            $data['encryptInfo'] = $encryptInfo;
        }

        print_r($data);
        
        //无须签名的情况
        if( !$isSign ) {
            $data['data'] = $param['data'];
            return $data;
        }
        
        //加密
        if ( !empty($data['encryptInfo']) )
        {
            $encrypt_string         = json_encode($data['encryptInfo'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
            $encryptInfo            = $this->encrypt($encrypt_string);
            $data['encryptInfo']    = $encryptInfo;
            $data['data']           = $param['data'];
            $data['signature']      = $this->AESsign(json_encode($data,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES));
        }
        else 
        {
            $data['data']           = $param['data'];
        }
        
        return Normal::returnCode(Normal::SUC,"签名处理成功",$data);
    }
    
    /**
     * @param $plainData:明文
     * @return array:加密后的数组
     */
    private function encrypt($plainData) 
    {
        $key                = $this->getRandStr(16);
        // AES加密数据
        $encryptInfoStr     = base64_encode($this->AesEncrypt($key, $plainData));
        // RSA加密信封
        $dgtlEnvlp          = base64_encode($this->RSAEncrypt($key));
        $encryptInfo = [
            'encryptInfo'   => $encryptInfoStr,
            'dgtlEnvlp'     => $dgtlEnvlp
        ];
        return $encryptInfo;
    }
    
    /**
     * Desc 使用RSA公钥分段加密
     * @param $plainData:明文
     * @return string:加密后的密文
     */
    private function RSAEncrypt($plainData)
    {
        $tokenInfo = $this->userLogin();
        //读取公钥文件
        $pubKey = "-----BEGIN PUBLIC KEY-----
            {$tokenInfo['data']['ysPublicKey']}
-----END PUBLIC KEY-----";
        // 转换为openssl密钥
        $publicKey = openssl_pkey_get_public($pubKey);
        $dataArray = str_split($plainData, self::MAX_ENCRYPT_BLOCK);
        $encrypted = [];
        foreach($dataArray as $subData){
            openssl_public_encrypt($subData, $enc, $publicKey);
            $encrypted[] = $enc;
        }
        // 释放资源
        openssl_free_key($publicKey);
        return implode('',$encrypted);
    }
    
    /**
     * Desc 使用RSA私钥分段解密
     * @param $encryptData:密文
     * @return string:解密后的明文
     */
    private function RSAdecrypt($encryptData) 
    {
        //读取私钥文件
        $priKey = "-----BEGIN RSA PRIVATE KEY-----
{$this->config['ys_private_key']}
-----END RSA PRIVATE KEY-----";       
        // 转换为openssl密钥
        $privateKey = openssl_get_privatekey($priKey);
        $decrypted = [];
        $dataArray = str_split($encryptData, self::MAX_DECRYPT_BLOCK);
        foreach($dataArray as $subData){
            openssl_private_decrypt($subData, $dec, $privateKey);
            $decrypted[] = $dec;
        }
        // 释放资源
        openssl_free_key($privateKey);
        return implode('',$decrypted);
    }
    
    /**
     * Desc 私钥加签
     * @param $plainData:明文
     * @return string:签名
     */
    private function RSAsign($plainData)
    {
        //读取私钥文件
        $priKey = "-----BEGIN RSA PRIVATE KEY-----
{$this->config['ys_private_key']}
-----END RSA PRIVATE KEY-----";
        // 转换为openssl密钥
        $privateKey = openssl_pkey_get_private($priKey);
        $dataArray = str_split($plainData, self::MAX_ENCRYPT_BLOCK);
        $encrypted = [];
        foreach($dataArray as $subData){
            openssl_private_encrypt($subData, $enc, $privateKey);
            $encrypted[] = $enc;
        }
        // 释放资源
        openssl_free_key($privateKey);
        return implode('',$encrypted);
    }
    
    /**
     * Desc 公钥验签
     * @param $encryptData:原始串
     * @return bool
     */
    private function RSAverify($encryptData) 
    {
        //读取公钥文件
        $pubKey = $this->config['ys_public_key'];
        // 转换为openssl密钥
        $publicKey = openssl_get_publickey($pubKey);
        $decrypted = [];
        $dataArray = str_split($encryptData,self::MAX_DECRYPT_BLOCK);
        foreach($dataArray as $subData){
            $result = openssl_public_decrypt($subData, $dec, $publicKey);
            $decrypted[] = $dec;
        }
        // 释放资源
        openssl_free_key($publicKey);
        return implode('',$decrypted);
    }
    
    /**
     * Desc AES加密
     * @param $key:密钥
     * @param $data:内容
     * @return string:加密后的密文
     */
    private function AESEncrypt($key, $data)
    {
        return openssl_encrypt($data,'AES-128-ECB', $key,OPENSSL_RAW_DATA);
    }
    
    /**
     * Desc AES解密
     * @param $key:密钥
     * @param $data:密文数据
     * @return string
     */
    private function AESDecrypt($key, $data) 
    {
        return openssl_decrypt($data, 'AES-128-ECB', $key, OPENSSL_RAW_DATA);
    }
    
    /**
     * @param $data:参数
     * @return string
     */
    private function AESsign($data)
    {
        return base64_encode($this->RSAsign($data));
    }
    
    private function getRandStr($length) 
    {
        $str = 'abcdefghijklmnopqrstuvwxyz0123456789';
        $len = strlen($str)-1;
        $randstr = '';
        for ($i=0;$i<$length;$i++) {
            $num=mt_rand(0,$len);
            $randstr .= $str[$num];
        }
        return $randstr;
    }
    
    private function getRandNumber($length) 
    {
        $str = '0123456789';
        $len = strlen($str)-1;
        $randstr = '';
        for ($i=0;$i<$length;$i++) {
            $num=mt_rand(0,$len);
            $randstr .= $str[$num];
        }
        return $randstr;
    }
    
    /**
     * 发起请求
     * @param string $method
     * @param array $param
     * @return mixed
     */
    public function execute(string $method,array $param)
    {
        if( empty($this->config[$method]) ) {
            return Normal::returnCode(Normal::FAIL,"业务方法{$method}路由不存在!");
        }
        
        if( empty($this->config['gateway']) ) {
            return Normal::returnCode(Normal::FAIL,"易生支付网关gateway未配置!");
        }
        
        $router = $this->config[$method];
        $url = $this->config['gateway'].$router;
        $result_sign = $this->getSign($method,$param);
        if( $result_sign['code'] != Normal::SUC ) {
            return $result_sign;
        }
        
        $postData = $result_sign['data'];
        $start_time = microtime(true);
        $header     = [
            'Content-Type: application/json; charset=utf-8',
        ];
        $response = Http::apiRequest($url,$postData,'post',$header,30,false,'json');
        $httpCode = Http::getHttpCode();
        MicroServiceLog::instance()->addLog([
            'interface_code'    => 'easypay_'.$method,
            'request_url'       => $url,
            'request_param'     => json_encode($postData,JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
            'response_code'     => $httpCode,
            'response_data'     => $response,
            'req_used_time'     => microtime(true) - $start_time,
        ]);

        $resHttp = json_decode($response, true);
        $errorMsg = $resHttp['message']??'请求失败';
        if( isset($resHttp['code']) && $resHttp['code'] == '00' ) {
            return Normal::returnCode(Normal::SUC,"处理成功",$resHttp);
        }
        return Normal::returnCode(Normal::FAIL,$errorMsg,$resHttp);
    }    
}